package ro.upt.ac.cstaicu.instrumentation.methodadapters;

import java.util.ArrayList;
import java.util.Collection;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import ro.upt.ac.cstaicu.analyze.Entity;
import ro.upt.ac.cstaicu.instrumentation.Predicate;

public class IMMethodAdapter extends MethodAdapter {

	private int currentLineNumber;
	private String className;
	private String methodName;
	
	private static int predicateIndex = 0;
	//for testing
	private static int predicateIndexInsideClass = 0;
	private Collection<Entity> predicates = new ArrayList<Entity>();
	
	private String loggerClass = "PredicateCounterLogger";

	public Collection<Entity> getPredicates() {
//		if (className.equals("com.google.inject.spi.InjectionPoint")) {
//			System.out.println("ADAPTER");
//			for (Entity e : predicates)
//				System.out.println(e);
//		}
		return predicates;
	}

	public IMMethodAdapter(MethodVisitor mv, String className, String methodName) {

		super(mv);
		this.className = className;
		this.methodName = methodName;

	}

	/**
	 * Saves the parameter lineNo in a private field.
	 */
	@Override
	public void visitLineNumber(int lineNo, Label arg1) {

		currentLineNumber = lineNo;
		super.visitLineNumber(lineNo, arg1);

	}
	
	public void visitLabel(Label arg0) {
//		mv.visitMethodInsn(Opcodes.INVOKESTATIC, "ro/upt/ac/cstaicu/instrumentation/logging/XMLGlobalLogger", "getInstance", "()Lro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass + ";");
//		mv.visitLdcInsn(arg0.hashCode());
//		mv.visitLdcInsn(currentLineNumber);
//		mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "ro/upt/ac/cstaicu/instrumentation/logging/XMLGlobalLogger", "resolveLabel", "(II)V");
		super.visitLabel(arg0);
//		System.out.println(currentLineNumber + " " + arg0.hashCode());
	}

	@Override
	public void visitCode() {
		super.visitCode();
		mv.visitMethodInsn(Opcodes.INVOKESTATIC, "ro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass, "getInstance", "()Lro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass + ";");
		mv.visitLdcInsn(className);
		mv.visitLdcInsn(methodName);
		mv.visitInsn(Opcodes.ACONST_NULL);
		mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "ro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass, "logFunctionCall", "(Ljava/lang/String;Ljava/lang/String;Ljava/util/ArrayList;)V");
	}

	@Override
	public void visitInsn(int arg0) {
		if (arg0 == Opcodes.RETURN) {
			mv.visitMethodInsn(Opcodes.INVOKESTATIC, "ro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass, "getInstance", "()Lro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass + ";");
			mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "ro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass, "functionEnded", "()V");
		}
		super.visitInsn(arg0);
	}

	@Override
	public void visitJumpInsn(int opcode, Label label) {
		
		if (opcode != Opcodes.GOTO) {

			if (oneOperandJump(opcode))
				duplicateTopElements(1);
			else
				duplicateTopElements(2);

			tracePredicate(opcode, label);

		}

		super.visitJumpInsn(opcode, label);
//		System.out.println("JMP " + label.hashCode());
		predicateIndexInsideClass++;
	}
	
	

	private void tracePredicate(int opcode, Label label) {

		Label l = new Label();
		Label l2 = new Label();

		super.visitJumpInsn(opcode, l);
		mv.visitMethodInsn(Opcodes.INVOKESTATIC, "ro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass,
				"getInstance", "()Lro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass + ";");
		mv.visitLdcInsn(predicateIndex);
		mv.visitLdcInsn(opcode);
		mv.visitLdcInsn(currentLineNumber);
		mv.visitLdcInsn(false);
		super.visitJumpInsn(Opcodes.GOTO, l2);

		super.visitLabel(l);
		mv.visitMethodInsn(Opcodes.INVOKESTATIC, "ro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass,
				"getInstance", "()Lro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass + ";");
		mv.visitLdcInsn(predicateIndex);
		mv.visitLdcInsn(opcode);
		mv.visitLdcInsn(currentLineNumber);
		mv.visitLdcInsn(true);

//		if (className.equals("com.google.inject.spi.InjectionPoint")) {
//			System.out.println(currentLineNumber + " " + className + "_" + predicateIndexInsideClass + " " + opcode + " " + predicateIndex);
//		}
		super.visitLabel(l2);
		mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "ro/upt/ac/cstaicu/instrumentation/logging/" + loggerClass,
				"logPredicate", "(IIIZ)V");
		Entity predEnt = 
			new Entity(new Predicate(predicateIndex, className, methodName, currentLineNumber, className + "_" + predicateIndexInsideClass), Entity.PREDICATE);
//		if (predicateIndex==619)
//			System.out.println("ENTITY = " + predEnt);
		predicates.add(predEnt);
//		predicates.add(new Predicate(predicateIndex, className, methodName, currentLineNumber));
		predicateIndex++;
	}
	
	public static void resetPredicateClassCounter() {
		predicateIndexInsideClass = 0;
	}


	private void duplicateTopElements(int no) {

 		if (no > 1) {
			super.visitInsn(Opcodes.SWAP);
			super.visitInsn(Opcodes.DUP_X1);
			super.visitInsn(Opcodes.SWAP);
			super.visitInsn(Opcodes.DUP_X1);
		} else
			super.visitInsn(Opcodes.DUP);

	}


	@Override
	public void visitMaxs(int maxStack, int maxLocals) {

		super.visitMaxs(maxStack + 5, maxLocals);

	}

	public boolean oneOperandJump(int opcode) {
		return (opcode == Opcodes.IFEQ || opcode == Opcodes.IFGE ||
				opcode == Opcodes.IFGT || opcode == Opcodes.IFLE ||
				opcode == Opcodes.IFNE || opcode == Opcodes.IFLT ||
				opcode == Opcodes.IFNULL || opcode == Opcodes.IFNONNULL);
	}

}
